"""

Function copy_ast(node) is an example of how to walk through an AST.

A node in an AST is determined by its class and the keyword arguments
passed to the class when the node was constructed.

We assume that the translation of a node (for a given translator) is
determined by the class of the node and the translation of these
keyword arguments.  In particular, the translation of a node is not
affected by its ancestors.

Suppose we are translating from Alice to Bob.  We do not assume that
the translation of an Alice node is a Bob node.  Instead, we assume
that the translation is some number of Bob nodes, together with other
information.  

The other information is used to store things that can't be handled
just now.  For example, for Python to JavaScript we need to store
information about variables, so that at the appropriate level we can
declare variables.  The Translation class embodies this assumption
about translation.

We provide copy_translator(cls, **kwargs) as an example of a
translation function.  Like every translation function, it has to
compute the translation of a node from its class and the translation
of the values used to construct the node.  Note that the translation
of a constant is itself, and the translation of a list is the list of
translated values.

Function copy_translator is an example of how to write a
translator. Clearly, in most cases something more interesting will be
done.

Finally, translate_ast(translator, node) is a driver that applies a
translator to a node.  It handles the recursion, and returns a
Translation object.

All this provides a framework that handles the recursion involved in
translation, and allows us to focus on the specific task of
translating Python to JavaScript (which is completely absent from this
file).

"""


import ast


CONSTANT_TYPES = (int, str, )   # TODO: Add missing values.


def copy_ast(node):
    """
    Returns copy of AST mode.  Walks through tree, copying as we go.

    The function relies on properties of the Python implementation of
    AST, and its source shows how they can be used.  Copy is, of
    course, the simplest form of translation.  However, the copy has
    new AST nodes.  Changing nodes in one tree should not affect the
    other.

    Here is an example of the difference:
        a = b = []              # Two references to one list.
        a = []; b = []          # Each reference has its own list.

    The functions works for any AST node, not just those for Python
    source.
    """

    # We will return cls(**kwargs), a copy of the node.
    kwargs = {}
    cls = node.__class__
    
    # From the fields on the node, compute the args dictionary.
    sentinel = []
    for key in node._fields:

        # If the node has a value for the key ...
        val = node.__dict__.get(key, sentinel)
        if val is not sentinel:

            # ... ast nodes are recursively transformed
            if isinstance(val, ast.AST):
                val = copy_ast(val)

            # ... as are lists
            elif isinstance(val, list):
                val = [copy_ast(x) for x in val]

            # ... but constants are not transformed at all
            elif type(val) in CONSTANT_TYPES:
                pass

            # ... and anything else is an error.
            else:
                raise Error

            # Now add (key, val) to the args dictionary.
            kwargs[key] = val

    return cls(**kwargs)


class Translation(object):
    """
    The translation of a node has a value, and other information.

    The value can be any number of nodes for the target language.
    Anything else is stored in the other attribute.
    """
    def __init__(self, value, other=None):

        self.value = value
        self.other = other


def copy_translator(cls, **kwargs):
    """
    From class of node and translation of fields, compute translation.
    """

    cls_kwargs = {}
    for key, trans in kwargs.iteritems():

        if isinstance(trans, list):
            value = [t.value for t in trans]
        elif type(trans) in CONSTANT_TYPES:
            value = trans
        else:
            value = trans.value
            assert isinstance(value, ast.AST)
        cls_kwargs[key] = value

        # We don't do anything with the 'other' attribute.
        pass

    # We could if we wish use change the class here.
    value = cls(**cls_kwargs)
    return Translation(value=value)


def translate_ast(translator, node):

    # Inner function to store translator in a closure.
    def translate(node):

        kwargs = {}
        cls = node.__class__

        sentinel = []
        for key in node._fields:

            val = node.__dict__.get(key, sentinel)
            if val is not sentinel:

                # Compute and store the translation of the val.
                if isinstance(val, ast.AST):
                    trans = translate(val)
                elif isinstance(val, list):
                    trans = [translate(x) for x in val]
                elif type(val) in CONSTANT_TYPES:
                    trans = val
                else:
                    raise Error
                kwargs[key] = trans

        # Apply translator to compute translation of node.
        return translator(cls, **kwargs)

    # Apply inner function to translate the given node.
    return translate(node)


if __name__ == '__main__':

    x = ast.parse('x = (a == b) or (c == 3);\nimport wobble as wibble\n')
    y = copy_ast(x)
    assert x is not y
    assert ast.dump(x) == ast.dump(y)

    z = translate_ast(copy_translator, x)
    assert ast.dump(x) == ast.dump(z.value)
    print 'done'




